home *** CD-ROM | disk | FTP | other *** search
- /*
- * - Don't enable gray line in Voice menu.
- * - Save voice name in preferences.
- * - Don't use any fonts that don't contain all the required characters.
- * (upper/lower letters, digits). Remove from menu if chars missing.
- * - Using CharWidth ('W') is too narrow for some fonts like Black Chancery
- * which seem to have wide side bearings. But f.widMax is too wide for some
- * fonts like Chicago, so that characters aren't drawn as big as they really
- * could be.
- */
-
- /*
- * Colin's ABC's -- Application to display characters and speak any character
- * that's clicked on or typed on the keyboard.
- *
- * Written for my two-year-old, Colin.
- *
- * 01 Nov 93 Paul DuBois dubois@primate.wisc.edu
- *
- * 01 Nov 93 Release 1.00
- * - Created.
- * 03 Dec 93 Release 1.02
- * - Made some changes to ControlBar library (release 1.01) that affect
- * declaration of callback functions here.
- * 16 Dec 93 Release 1.03
- * - Moved Talk Panel menu item from Options menu to Voice menu. Since this
- * means the main window isn't necessarily always in front, the menu hook needs
- * to set more of the menu items properly according to what the front window is.
- * Also took the idle procedure and attached it to the main window so it only
- * runs to speak characters when main window is in front.
- * - Added event inspection hook that causes autoKey events to be discarded.
- * This avoids the problem that occurs when the child leans on a key for a
- * while -- the queue gets many instances of the same character quickly.
- * - Added Flush Queue item to Options menu that flushes the queue of
- * characters waiting to be spoken.
- * 17 Jan 94 Release 1.05
- * - Use SkelAlert() rather than CenteredAlert(). Requires TransSkel 3.07.
- * - Message.[ch] stuff revised slightly.
- * 25 Jan 94
- * - Window can be resized, should that be desired. Buttons and characters inside
- * them resize as well. Because of this, I no longer use pushbutton controls
- * for character buttons. Instead, I do the drawing and tracking myself.
- * - Initial standard state of window fills screen, to help prevent mouse clicks
- * outside the window that bring another application forward. Initial user state
- * of window is its minimum size, positioned centrally on the screen.
- * 26 Jan 94
- * - Added Font menu for changing button character font.
- * 31 Jan 94
- * - Uses Prefs file to record font name and lettercase preferences.
- * 01 Feb 94
- * - Record window user state in preferences.
- * 02 Feb 94
- * - Does't dim Voice menu items when TalkPanel window is in front.
- * - Record voice name in preferences.
- *
- * 20 Feb 94 Release 1.07
- * - Updated for TransSkel 3.11, ControlBar 1.05, TalkStuff 1.06, TalkPanel 1.05
- * (all of which now use Pascal function bindings).
- */
-
- # include <Sound.h>
-
- # include "TransSkel.h"
-
- # include "Message.h"
- # include "ControlBar.h"
- # include "TalkStuff.h"
- # include "TalkPanel.h"
-
- # include "C-ABC.h"
- # if ENABLE_DEBUG
- # include "Debug.h"
- # endif
-
-
- /* \325 = left single curly quote */
-
- # define bailString "\pColin\325s ABC\325s"
-
- # define lowerDisplayHt 55
- # define speakingOffset 170
- # define speakingStr "\pSpeaking:"
-
- # define btnHMargin 10
- # define btnTopMargin 10
- # define btnBottomMargin 10
-
- # define btnCurvature 12
-
- # define btnMinHSize 20
- # define btnMinHGap 6
- # define btnMinVSize 20
- # define btnMinVGap 6
-
- # define gapRatio 3
-
- # define maxQueue 100 /* max characters that can be queued up */
-
-
- typedef enum /* resource numbers */
- {
- aboutAlrtRes = 1000,
- authorAlrtRes,
- msgeAlrtRes,
-
- fileMenuNum = 1000,
- editMenuNum,
- voiceMenuNum,
- fontMenuNum,
- optionsMenuNum,
-
- clickWindRes = 1000
- };
-
-
- typedef enum /* File menu item numbers */
- {
- quitApp = 1
- };
-
-
- typedef enum /* Options menu item numbers */
- {
- upperCaseItem = 1,
- lowerCaseItem,
- oSepLine1,
- flushQueue
- };
-
-
- static void Terminate (void);
- static void Bail (StringPtr msg);
-
- static pascal void BarSetVolume (ControlHandle bar);
-
-
- static ControlHandle volumeBar;
- static short origVolume;
-
- /*
- * Bar is positioned after it's created
- */
-
- static BarInfo barInfo =
- {
- &volumeBar, "\pVolume = ", 0, 0, 0, 140,
- 0, 7, 0, { 0, "\p" }, BarSetVolume, nil
- };
-
- static WindowPtr clickWind;
-
- static MenuHandle fileMenu;
- static MenuHandle editMenu;
- static MenuHandle voiceMenu;
- static MenuHandle fontMenu;
- static MenuHandle optionsMenu;
-
- static char queue[maxQueue];
- static int queueLen = 0;
- static char currentChar = '\0';
- static Rect charDispRect;
-
-
- static short btnHSize = btnMinHSize;
- static short btnVSize = btnMinVSize;
- static short btnHGap = btnMinHGap;
- static short btnVGap = btnMinVGap;
-
- static short actualBtnHMargin = btnHMargin;
-
- static short size[] = { 9, 10, 12, 14, 18, 20, 24, 32, 36, 48, 72, 0 };
- static short sizeIndex = 0;
-
- static Prefs prefs;
- static short fontNum = -1; /* -1 so first SetFont() does something */
-
-
- /* ------------------------------------------------------------------ */
-
- /*
- * Sound volume management
- */
-
-
- /*
- * Get the volume.
- */
-
- static short
- GetVolume (void)
- {
- short value;
-
- GetSoundVol (&value);
- return (value);
- }
-
-
- /*
- * Set the volume, if it the new value is different than the current.
- */
-
- static void
- SetVolume (short value)
- {
- if (value != GetVolume ())
- SetSoundVol (value);
- }
-
-
- /*
- * Procedure that's called when the volume bar control value is changed.
- */
-
- static pascal void
- BarSetVolume (ControlHandle bar)
- {
- SetVolume (GetCtlValue (bar));
- }
-
-
- /* ------------------------------------------------------------------ */
-
- /*
- * Character queue management
- */
-
-
- /*
- * Put a character in the queue. Return non-zero if sucessful,
- * zero if queue is full.
- */
-
- static int
- QueueChar (char c)
- {
- if (queueLen >= maxQueue)
- return (0);
- queue[queueLen++] = c;
- return (1);
- }
-
-
- /*
- * Pull a character off the queue and return it. Return '\0' if empty.
- */
-
- static char
- DequeueChar (void)
- {
- char c;
-
- if (queueLen == 0)
- return ('\0');
- c = queue[0];
- BlockMove (queue + 1, queue, --queueLen);
- return (c);
- }
-
-
- /*
- * Flush the queue of any outstanding characters.
- */
-
- static void
- FlushQueue (void)
- {
- queueLen = 0;
- }
-
-
- /* ------------------------------------------------------------------ */
-
- /*
- * Window layout and content management
- */
-
-
- static char
- GetCharButtonChar (short i)
- {
- if (i < 26) /* it's a letter */
- return ((prefs.letterCase == upperCase ? 'A' : 'a') + i);
- /* it's a digit */
- return ('0' + (i - 26));
- }
-
-
- static void
- GetCharButtonRect (short i, Rect *r)
- {
- short h, v;
-
- if (i < 26) /* it's a letter */
- {
- h = (i % 13) * (btnHSize + btnHGap);
- v = (i / 13) * (btnVSize + btnVGap);
- }
- else /* it's a digit */
- {
- h = (i - 26) * (btnHSize + btnHGap) + (13 - 10) * (btnHSize + btnHGap) / 2;
- v = 2 * (btnVSize + btnVGap);
- }
- SetRect (r, 0, 0, btnHSize, btnVSize);
- OffsetRect (r, actualBtnHMargin + h, btnTopMargin + v);
- }
-
-
- /*
- * Calculation of rect within which currently spoken character is displayed
- */
-
- static void
- CalcCharDispRect (void)
- {
- Rect r;
-
- r = clickWind->portRect;
- SetRect (&charDispRect, 0, 0, 16, 16);
- OffsetRect (&charDispRect,
- r.left + speakingOffset + StringWidth (speakingStr) + 5,
- r.bottom - 32);
- }
-
-
- /*
- * Invalidate character button portion of display to force redraw
- */
-
- static void
- InvalButtonArea (void)
- {
- Rect r;
-
- r = clickWind->portRect;
- r.bottom -= lowerDisplayHt;
- InvalRect (&r);
- }
-
-
- /*
- * Calculate size and spacing of character buttons
- */
-
- static Boolean
- TestLayout (short max, short btnSize, short gapSize, short nButtons)
- {
- return ((btnSize + gapSize) * nButtons - gapSize <= max);
- }
-
-
- static void
- CalcButtonLayout (void)
- {
- Rect r;
- short maxHSize;
- short maxVSize;
- short steps;
- short loop;
-
- r = clickWind->portRect;
- maxHSize = (r.right - r.left) - 2 * btnHMargin;
- maxVSize = (r.bottom - r.top) - btnTopMargin - btnBottomMargin - lowerDisplayHt;
-
- /*
- * Start with minimum size buttons and gaps, then increase size to max
- * that will fit. Increase the gap every gapRatio steps, increase button
- * size every step.
- */
-
- btnHSize = btnMinHSize;
- btnVSize = btnMinVSize;
- btnHGap = btnMinHGap;
- btnVGap = btnMinVGap;
- steps = 0;
- loop = 1;
- while (loop)
- {
- /* loop as long as some increase in size takes place */
-
- loop = 0;
- if (steps >= gapRatio)
- steps = 0;
- if (++steps == 1)
- {
- if (TestLayout (maxHSize, btnHSize, btnHGap + 1, 13))
- {
- ++btnHGap;
- loop = 1;
- }
- if (TestLayout (maxVSize, btnVSize, btnVGap + 1, 3))
- {
- ++btnVGap;
- loop = 1;
- }
- }
- if (TestLayout (maxHSize, btnHSize + 1, btnHGap, 13))
- {
- ++btnHSize;
- loop = 1;
- }
- if (TestLayout (maxVSize, btnVSize + 1, btnVGap, 3))
- {
- ++btnVSize;
- loop = 1;
- }
- }
- actualBtnHMargin = btnHMargin
- + (maxHSize - ((btnHSize + btnHGap) * 13 - btnHGap)) / 2;
- }
-
-
- static short
- FontCharWidth (void)
- {
- short i, wid, maxWid;
-
- maxWid = 0;
- for (i = 0; i < 36; i++)
- {
- wid = CharWidth (GetCharButtonChar (i));
- if (maxWid < wid)
- maxWid = wid;
- }
- return (maxWid);
- }
-
-
- /*
- * Calculate biggest size for current font that will fit in the buttons
- */
-
- static void
- CalcFontSize (void)
- {
- FontInfo f;
- Rect r;
- short ht, wid, i;
-
- GetCharButtonRect (0, &r); /* get a button rect so know how big it is */
- ht = r.bottom - r.top;
- wid = r.right - r.left;
- sizeIndex = 0; /* size[0] always fits, but design */
- for (i = 1; size[i] != 0; i++)
- {
- TextSize (size[i]);
- GetFontInfo (&f);
- if (FontCharWidth () + 4 > wid) /* too wide */
- break;
- if (f.ascent + f.descent + f.leading + 4 > ht) /* too tall */
- break;
- sizeIndex = i;
- }
- TextSize (0); /* restore to normal size */
- }
-
-
- /*
- * Set the current font.
- *
- * GetFNum() returns 0 if the font name is the sytem font or unknown.
- * That's okay -- just use system font if font isn't known.
- */
-
- static void
- SetFont (StringPtr fontName)
- {
- short newFontNum;
-
- GetFNum (fontName, &newFontNum);
- if (fontNum != newFontNum)
- {
- BlockMove (fontName, prefs.fontName, (long) (fontName[0] + 1));
- fontNum = newFontNum;
- CalcFontSize ();
- InvalButtonArea (); /* force redraw of button area */
- }
- }
-
-
- /*
- * Position the volume scroll in the lower display area
- */
-
- static void
- PositionVolumeBar (void)
- {
- MoveControl (volumeBar, 10, clickWind->portRect.bottom - 30);
- }
-
-
- /*
- * Draw grow box of dispWind in lower right hand corner
- */
-
-
- static void
- DrawGrowBox (WindowPtr w)
- {
- RgnHandle oldClip;
- Rect r;
-
- r = w->portRect;
- r.left = r.right - 15; /* draw only in corner */
- r.top = r.bottom - 15;
- oldClip = NewRgn ();
- GetClip (oldClip);
- ClipRect (&r);
- DrawGrowIcon (w);
- SetClip (oldClip);
- DisposeRgn (oldClip);
- }
-
-
- /*
- * Mouse was clicked in a region. Track it and return non-zero if mouse
- * ended up still in region.
- *
- * It seems like you should just be able to create a second region that's the
- * same as rgn but inset by one pixel, and invert that to avoid inverting rgn
- * and re-framing it. But that doesn't always seem to work. Hm.
- */
-
- static Boolean
- TrackRgn (RgnHandle rgn)
- {
- short i;
- Boolean inRgn;
- Point pt;
-
- InvertRgn (rgn);
- FrameRgn (rgn);
- inRgn = true;
- while (StillDown ())
- {
- GetMouse (&pt);
- i = PtInRgn (pt, rgn);
- if ((inRgn && !i) || (!inRgn && i)) /* was in/out, now out/in */
- {
- InvertRgn (rgn);
- FrameRgn (rgn);
- inRgn = !inRgn;
- }
- }
- if (inRgn) /* mouse ended up in rgn */
- {
- InvertRgn (rgn);
- FrameRgn (rgn);
- }
- return (inRgn);
- }
-
-
- /* ------------------------------------------------------------------ */
-
- /*
- * Window handler routines
- */
-
-
- static pascal void
- Mouse (Point pt, long t, short mods)
- {
- RgnHandle rgn;
- ControlHandle ctrl;
- short partNo;
- Rect r;
- short i;
- char c;
-
- if ((partNo = FindControl (pt, clickWind, &ctrl)) != 0)
- {
- TrackBar (ctrl, pt, partNo);
- return;
- }
-
- /* check for click in character button */
-
- for (i = 0; i < 36; i++)
- {
- GetCharButtonRect (i, &r);
- if (PtInRect (pt, &r))
- {
- /* convert rect to region and track it */
- rgn = NewRgn ();
- OpenRgn ();
- FrameRoundRect (&r, btnCurvature, btnCurvature);
- CloseRgn (rgn);
- if (TrackRgn (rgn))
- QueueChar (GetCharButtonChar (i));
- break;
- }
- }
- }
-
-
- static pascal void
- Key (short c, short code, short mods)
- {
- (void) QueueChar (c);
- }
-
-
- /*
- * Update the click window.
- */
-
- static pascal void
- Update (Boolean resized)
- {
- FontInfo f;
- Rect r;
- short h;
- short i;
- char c;
-
- r = clickWind->portRect;
-
- /*
- * If window was resized, recalculate layout, invalidate the entire
- * window, and return. Another update will occur as a result of the
- * invalidate, at which point the actual drawing can be done.
- */
-
- if (resized)
- {
- CalcButtonLayout ();
- CalcFontSize ();
- CalcCharDispRect ();
- PositionVolumeBar ();
- InvalRect (&r);
- return;
- }
-
- EraseRect (&r);
-
- MoveTo (0, r.bottom - lowerDisplayHt);
- LineTo (r.right, r.bottom - lowerDisplayHt);
-
- /* draw character buttons */
-
- TextFont (fontNum);
- TextSize (size[sizeIndex]);
- GetFontInfo (&f);
- for (i = 0; i < 36; i++)
- {
- GetCharButtonRect (i, &r);
- EraseRoundRect (&r, btnCurvature, btnCurvature);
- FrameRoundRect (&r, btnCurvature, btnCurvature);
- c = GetCharButtonChar (i);
- h = (r.left + r.right - CharWidth (c)) / 2;
- MoveTo (h, (r.top + r.bottom + f.ascent) / 2 - 1);
- DrawChar (c);
- }
- TextFont (0);
- TextSize (0);
-
- DrawControls (clickWind);
- r = (**volumeBar).contrlRect;
- InsetRect (&r, -2, -2);
- FrameRect (&r);
- DrawBarTitle (volumeBar);
- DrawBarValue (volumeBar, true);
-
- /* draw "character being spoken" information */
-
- r = clickWind->portRect;
- MoveTo (r.left + speakingOffset, r.bottom - 19);
- DrawString (speakingStr);
- if (currentChar != '\0')
- {
- h = (charDispRect.left + charDispRect.right - CharWidth (currentChar)) / 2;
- MoveTo (h, charDispRect.bottom - 3);
- DrawChar (currentChar);
- }
- DrawGrowBox (clickWind);
- }
-
-
- /*
- * When the window goes inactive, I only unhilite the volume bar, rather than
- * hiding and framing it as user interface guidelines dictate. I think an
- * empty frame looks worse than an unhilited scroll.
- */
-
- static pascal void
- Activate (Boolean active)
- {
- if (!active)
- HiliteControl (volumeBar, dimHilite);
- else
- {
- SetBarValue (volumeBar, GetVolume ());
- HiliteControl (volumeBar, normalHilite);
- }
- DrawGrowBox (clickWind);
- }
-
-
- static pascal void
- Clobber (void)
- {
- HideWindow (clickWind);
- DisposeWindow (clickWind);
- }
-
-
- /*
- * Window idle procedure. If the current character is done being said, erase it.
- * If there are more characters in the character queue, pull the first one off the
- * queue and begin saying it.
- */
-
- static pascal void
- Idle (void)
- {
- char buf[2];
- short h;
-
- if (BusyTalking ())
- return;
- if (currentChar != '\0')
- {
- EraseRect (&charDispRect);
- currentChar = '\0';
- }
- if ((currentChar = DequeueChar ()) != '\0')
- {
- h = (charDispRect.left + charDispRect.right - CharWidth (currentChar)) / 2;
- MoveTo (h, charDispRect.bottom - 3);
- DrawChar (currentChar);
- buf[0] = 1;
- buf[1] = currentChar;
- TalkStr ((StringPtr) buf, talkAsync);
- }
- }
-
-
- /*
- * Zoom the click window. This differs from the default zoom proc in that
- * it doesn't leave a 3-pixel border around the window, and it doesn't clip
- * to the grow rect.
- */
-
- static pascal void
- DoZoom (WindowPtr w, short zoomDir)
- {
- Rect r, growRect;
-
- EraseRect (&w->portRect);
- if (zoomDir == inZoomOut) /* zooming to default state */
- {
- /*
- * Get the usable area of the device containing most of the
- * window. (Can ignore the result because the rect is always
- * correct. Pass nil for device parameter because it's
- * irrelevant.) Then adjust rect for title bar height.
- */
- (void) SkelGetWindowDevice (w, nil, &r);
- r.top += SkelGetWindTitleHeight (w) - 1;
- (**(WStateData **)(((WindowPeek)w)->dataHandle)).stdState = r;
- }
- ZoomWindow (w, zoomDir, false);
- }
-
-
- /*
- * Initialize click window
- */
-
- static void
- SetupWindow (void)
- {
- Rect r, r2;
- short minHt, minWid;
- short titleHeight;
- Boolean useDefaultUserState = false;
-
- if (SkelQuery (skelQHasColorQD))
- clickWind = GetNewCWindow (clickWindRes, nil, (WindowPtr) -1L);
- else
- clickWind = GetNewWindow (clickWindRes, nil, (WindowPtr) -1L);
-
- titleHeight = SkelGetWindTitleHeight (clickWind);
-
- /*
- * Move and size window so it fills entire screen.
- * Also set minimum and maximum grow bounds.
- */
-
- (void) SkelGetWindowDevice (clickWind, (GDHandle *) nil, &r);
- r.top += titleHeight - 1;
- MoveWindow (clickWind, r.left, r.top, false);
- SizeWindow (clickWind, r.right - r.left, r.bottom - r.top, false);
-
- SkelWindow (clickWind,
- Mouse,
- Key,
- Update,
- Activate,
- nil,
- Clobber,
- Idle,
- true); /* idle only when frontmost */
-
- minWid = 2 * btnHMargin + 13 * btnMinHSize + 12 * btnMinHGap;
- minHt = btnTopMargin + btnBottomMargin
- + 3 * btnMinVSize + 2 * btnMinVGap + lowerDisplayHt;
-
- SkelSetGrowBounds (clickWind, minWid, minHt,
- (r.right - r.left) + 1, (r.bottom - r.top) + 1);
-
- /*
- * - Install alternate zoom proc
- * - Set standard state to full screen
- * - Set user state as follows: If there is a user state specified in the
- * preferences that's entirely visible, use it. If there was no preferences
- * user state (rect in preferences is empty), or the rect specified in the
- * preferences file is not entirely visible on the desktop, set the user
- * state to the minimum window size, centered on screen.
- */
-
- SkelSetZoom (clickWind, DoZoom);
- (**(WStateData **)(((WindowPeek)clickWind)->dataHandle)).stdState = r;
-
- r2 = prefs.userState;
- if (EmptyRect (&r2))
- useDefaultUserState = true;
- else
- {
- /* test whether window (incl. title bar) will be visible in this position */
- r2.top -= titleHeight;
- useDefaultUserState = !SkelTestRectVisible (&r2);
- r2.top += titleHeight;
- }
- if (useDefaultUserState)
- {
- /* use minimum window size, centered on screen */
- r2 = r;
- r2.right = r2.left + minWid;
- r2.bottom = r2.top + minHt;
- SkelPositionRect (&r, &r2, FixRatio (1, 2), FixRatio (1, 5));
- }
- (**(WStateData **)(((WindowPeek)clickWind)->dataHandle)).userState = r2;
-
- TextFont (0); /* 0 = system font */
- TextSize (0); /* 0 = system size */
-
- SetFont (prefs.fontName);
- CalcButtonLayout ();
- CalcFontSize ();
- CalcCharDispRect ();
-
- MakeBar (&barInfo, clickWind);
- PositionVolumeBar ();
-
- SetBarValue (volumeBar, GetVolume ());
- }
-
-
- /* ------------------------------------------------------------------ */
-
- /*
- * Menu stuff
- */
-
-
- /*
- Handle selection of About... item from Apple menu
- */
-
- static pascal void
- DoAbout (short item)
- {
- if ((SkelGetModifiers () & optionKey) == 0)
- (void) SkelAlert (aboutAlrtRes, SkelDlogFilter (nil, true),
- skelPositionOnParentDevice);
- else
- (void) SkelAlert (authorAlrtRes, SkelDlogFilter (nil, true),
- skelPositionOnParentDevice);
- SkelRmveDlogFilter ();
- }
-
-
- static pascal void
- DoFileMenu (short item)
- {
- Fixed result;
-
- switch (item)
- {
- case quitApp:
- SkelStopEventLoop ();
- break;
- }
- }
-
-
- /*
- * Handle Edit menu. This is only used for DA's since the application
- * does no editing.
- */
-
- static pascal void
- DoEditMenu (short item)
- {
- (void) SystemEdit (item - 1);
- }
-
-
- static pascal void
- DoVoiceMenu (short item)
- {
- short nVoices;
-
- GetTalkVoiceNumRange ((short *) nil, &nVoices);
- if (item <= nVoices)
- {
- SetTalkVoiceNum (item);
- TalkVoiceNumToName (item, prefs.voiceName);
- }
- else
- TalkPanelShow ();
- }
-
-
- static pascal void
- DoFontMenu (short item)
- {
- Str255 fontName;
-
- GetItem (fontMenu, item, fontName);
- SetFont (fontName);
- }
-
-
- static pascal void
- DoOptionsMenu (short item)
- {
- Rect r;
-
- switch (item)
- {
- case upperCaseItem:
- if (prefs.letterCase != upperCase) /* do nothing unless there's a change */
- {
- prefs.letterCase = upperCase;
- InvalButtonArea (); /* force redraw of button area */
- }
- break;
- case lowerCaseItem:
- if (prefs.letterCase != lowerCase) /* do nothing unless there's a change */
- {
- prefs.letterCase = lowerCase;
- InvalButtonArea (); /* force redraw of button area */
- }
- break;
- case flushQueue:
- FlushQueue ();
- break;
- }
- }
-
-
- /*
- * Set enable/disable state of a set of menu items, and optionally
- * check one of them.
- *
- * If checkedItem is non-zero, check that item.
- */
-
- static void
- SetMenuItems (MenuHandle m, short min, short max, short checkedItem, Boolean enable)
- {
- short i, nItems;
-
- nItems = CountMItems (m);
-
- for (i = min; i <= max; ++i)
- {
- if (enable)
- EnableItem (m, i);
- else
- DisableItem (m, i);
- SetItemMark (m, i, i == checkedItem ? checkMark : noMark);
- }
- }
-
-
- /*
- * Adjust menus.
- *
- * Voice menu items are enabled if click window or TalkPanel window are frontmost.
- * Font and Options menu items are enabled if click window is frontmost.
- */
-
- static pascal void
- AdjustMenus (void)
- {
- Str255 mFontName;
- Boolean clickWindFront, talkPanelFront;
- Boolean enable;
- short i, nItems;
-
- clickWindFront = (FrontWindow () == clickWind);
- talkPanelFront = (FrontWindow () == TalkPanelWindowPtr ());
-
- enable = clickWindFront || talkPanelFront;
-
- SetMenuItems (voiceMenu, 1, CountMItems (voiceMenu), GetTalkVoiceNum (), enable);
-
- enable = clickWindFront;
-
- /* Determine current font name item */
-
- nItems = CountMItems (fontMenu); /* # fonts in menu */
- for (i = 1; i <= nItems; ++i)
- {
- GetItem (fontMenu, i, mFontName); /* get font name */
- if (EqualString (prefs.fontName, mFontName, false, true))
- {
- CheckItem (fontMenu, i, true);
- break;
- }
- }
- SetMenuItems (fontMenu, 1, nItems, i, enable);
-
- SetMenuItems (optionsMenu, upperCaseItem, lowerCaseItem,
- prefs.letterCase == upperCase ? upperCaseItem : lowerCaseItem,
- enable);
- SetMenuItems (optionsMenu, flushQueue, flushQueue, 0, enable);
- }
-
-
- /*
- * Initialize menus and menu handlers.
- * \325 is curly right quote.
- * \311 is the ellipsis character.
- */
-
- static void
- SetupMenus (void)
- {
- SkelApple ("\pAbout Colin\325s ABC\325s\311", DoAbout);
-
- if ((fileMenu = GetMenu (fileMenuNum)) == (MenuHandle) nil
- || !SkelMenu (fileMenu, DoFileMenu, nil, false, false))
- Bail ("\pProblem creating File menu.");
-
- if ((editMenu = GetMenu (editMenuNum)) == (MenuHandle) nil
- || !SkelMenu (editMenu, DoEditMenu, nil, false, false))
- Bail ("\pProblem creating Edit menu.");
-
- if ((voiceMenu = GetMenu (voiceMenuNum)) == (MenuHandle) nil
- || !SkelMenu (voiceMenu, DoVoiceMenu, nil, false, false))
- Bail ("\pProblem creating Voice menu.");
- TalkFillVoiceMenu (voiceMenu);
- AppendMenu (voiceMenu, "\p(-;Talk Panel/T");
-
- if ((fontMenu = GetMenu (fontMenuNum)) == (MenuHandle) nil
- || !SkelMenu (fontMenu, DoFontMenu, nil, false, false))
- Bail ("\pProblem creating Font menu.");
- AddResMenu (fontMenu, 'FONT');
-
- if ((optionsMenu = GetMenu (optionsMenuNum)) == (MenuHandle) nil
- || !SkelMenu (optionsMenu, DoOptionsMenu, nil, false, false))
- Bail ("\pProblem creating Options menu.");
-
- DrawMenuBar ();
-
- SkelSetMenuHook (AdjustMenus);
- }
-
-
- /* ------------------------------------------------------------------ */
-
- /*
- * Event hook, idle processing, and suspend/resume stuff
- */
-
-
- /*
- * Event hook. On autoKey events, tell TransSkel the event was handled so that
- * it gets ignored. This helps avoid filling up the character queue when someone
- * leans on a key.
- */
-
- static pascal Boolean
- EventHook (EventRecord *event)
- {
- return (event->what == autoKey);
- }
-
-
- /*
- * Procedure to handle suspend/resume. This application provides its
- * own local volume control, but that volume may not be appropriate for
- * other applications.
- *
- * On a suspend, save the local volume and reset volume to global value.
- *
- * On a resume, restore the local volume UNLESS the global volume has
- * changed during the suspend. If it has, assume the user wants it to
- * apply locally, too.
- *
- * Handle suspend and resume events by activating/deactivating the
- * front window properly. Always deactivate on suspend. On resume,
- * only activate if there's not a mouse click for a window other than
- * the frontmost window in the event queue. If there is, it indicates
- * the user brought the application forward by clicking in a window
- * other than the frontmost one. Since that other window will soon
- * be activated, there's no point in activating the frontmost one.
- *
- * On suspend, don't bother setting the volume bar; it's going to be unhilited
- * anyway when the deactivate occurs. On resume, don't set bar either; the
- * activate procedure will resync the bar to the current volume and redraw as
- * necessary.
- */
-
- static pascal void
- MySuspendResume (Boolean inForeground)
- {
- WindowPtr w = FrontWindow ();
- EventRecord event;
- GrafPtr eventPort;
- Boolean doActivate = true;
- short newVolume;
- static short localVolume;
-
- if (!inForeground) /* we're being suspended */
- {
- StopTalking ();
- localVolume = GetVolume ();
- SetVolume (origVolume);
- }
- else /* resume is imminent */
- {
- newVolume = GetVolume ();
- if (newVolume != origVolume) /* user changed volume during suspend */
- origVolume = newVolume;
- else /* global volume unchanged; restore local */
- newVolume = localVolume;
- SetVolume (newVolume);
-
- if (EventAvail (mDownMask, &event))
- {
- (void) FindWindow (event.where, &eventPort);
- if (eventPort != w)
- doActivate = false;
- }
- }
- if (doActivate)
- SkelActivate (w, inForeground);
- }
-
-
- /* ------------------------------------------------------------------ */
-
-
- /*
- * Main program.
- */
-
- int
- main (void)
- {
- SkelInit ((SkelInitParamsPtr) nil);
-
- # if ENABLE_DEBUG
- SetupDebugWindow ();
- ShowDebugWindow ();
- # endif
-
- /*
- * Get global volume and bump it up locally if it's low
- * (speech is harder to hear than beeps).
- */
-
- origVolume = GetVolume (); /* get original volume */
- if (origVolume < 3)
- SetVolume (3);
-
- SetMessageAlertNum (msgeAlrtRes);
- if (!TalkInit ())
- Bail ("\pSpeech Manager was not found or could not be initialized.");
- if (!TalkPanelInit (100, 150))
- Bail ("\pTalk Panel could not be initialized.");
-
- /*
- * Initialize the preferences structure, then try to read the
- * preferences file to override the defaults.
- *
- * The default font name is the name of the system font. Don't
- * assume "Chicago"; instead, get the actual name by mapping
- * systemFont onto its name. Default userState is initialized to
- * an empty rectangle. If the prefs file doesn't override this,
- * the window initialization code will detect the empty rect and
- * set the user state to the default user state.
- */
-
- prefs.version = prefsVersion;
- TalkVoiceNumToName (1, prefs.voiceName);
- GetFontName (systemFont, prefs.fontName);
- prefs.letterCase = upperCase;
- SetRect (&prefs.userState, 0, 0, 0, 0);
-
- ReadPreferences (&prefs);
- SetTalkVoiceNum (TalkVoiceNameToNum (prefs.voiceName));
-
- SetupMenus ();
-
- SetupWindow ();
- ShowWindow (clickWind);
- SkelDoUpdates ();
-
- SkelSetEventHook (EventHook);
- SkelSetMenuHook (AdjustMenus);
- SkelSetSuspendResume (MySuspendResume);
-
- SkelEventLoop ();
-
- Terminate ();
- }
-
-
- /*
- * Termination routine
- */
-
- static void
- Terminate (void)
- {
-
- /* save final window user state in prefences */
-
- prefs.userState = (**(WStateData **)(((WindowPeek)clickWind)->dataHandle)).userState;
- WritePreferences (&prefs);
-
- # if ENABLE_DEBUG
- SkelPause (120L);
- # endif
-
- SetVolume (origVolume); /* restore original volume */
- TalkPanelCleanup ();
- TalkCleanup ();
- SkelCleanup ();
- ExitToShell ();
- }
-
-
- static void
- Bail (StringPtr msg)
- {
- Message4 (bailString, "\p: ", msg, "\p Bailing out.");
- Terminate ();
- }
-